Verilog基础:表达式位宽的确定(位宽拓展)

您所在的位置:网站首页 verilog 多位宽赋值 Verilog基础:表达式位宽的确定(位宽拓展)

Verilog基础:表达式位宽的确定(位宽拓展)

2024-07-12 06:41| 来源: 网络整理| 查看: 265

相关文章

Verilog基础专栏icon-default.png?t=N7T8https://blog.csdn.net/weixin_45791458/category_12263729.html

        Verilog中的位宽拓展就像是C语言中的类型转换一样(C语言:表达式运算的类型转换),用于统一表达式计算时的数据位宽,但是它又与类型转换存在一些区别,本文旨在详细阐述其细节。

表达式位宽

        如果想要在计算表达式时获得和谐一致的结果,那么控制表达式中的位宽就很重要。很多时候方法很简单。例如,如果在两个16位数据的reg变量上做位与操作,那么计算结果很显然就是16位。但是在某种情况下,计算应该用多少位或者结果应该是多少位就不那么明显。

        例如,对两个16位数据做加法操作是选择用16位进行计算呢,还是为了包含可能的进位而选择用17位进行计算呢?这里就涉及了Verilog用来确定表达式位宽的规则。

例1 reg [15 : 0] a, b; reg [15 : 0] sumA; reg [16 : 0] sumB; sumA = a + b; //赋值表达式右端按照a和b都为16位来计算,结果仍为16位,最后赋值给sumA且进位溢出; sumB = a + b; //a和b首先根据规则补零拓展(选择这种拓展是因为赋值表达式右端存在无符号数a,b)到17位,然后再执行相加,结果为17位,最后赋值给sumB,因此进位得以保留。 表达式位宽规则

为了在现实的情况下方便地解决位宽问题,Verilog规定了如下的表达式位宽规则。

表达式中操作数的位宽由表达式中的操作数本身或其所处的上下文决定。Verilog中所有表达式中的操作数分为自决定和上下文决定两类。

自决定表达式(self-determined expression)就是表达式中所有操作数(包括子表达式)的位宽完全由自己决定。

上下文决定表达式(context-determined expression)就是表达式中所有操作数(包括子表达式)的位宽由上下文环境中最大的位宽决定。

混合自决定和上下文决定表达式就是表达式中部分操作数位宽是自决定,部分位宽是上下文环境决定。

 下面的图1和图2给出了表达式的位宽规则,可以结合两者进行分析。

图1 各操作数位宽规则

图2 各表达式位宽规则

        根据以上两图,我们就可以知道例1的表达式位宽计算过程。首先我们从图1得知,=操作符右端的操作数(其实是一个子表达式)是由上下文环境决定位宽的,且根据注释我们还可以得知除了=右端的操作数,=操作符左端的操作数也会加入到上下文环境中(但它不会进行位宽拓展)。这是什么意思呢?我们可以用例1进行说明。

sumA = a + b;

        执行上面的语句时,因为+号两端的操作数是上下文环境决定位宽,且=左边的操作数也被加入上下文环境(这相当于进行了嵌套),所以操作数sumA,a,b首先都被加入上下文环境,此时这三个变量的位宽是相同的16位,所以没有数据会拓展,可以直接进行运算,根据图2,a+b结果的位宽为max(L(a), L(b)),所以是16位,进位被舍去(此时子表达式a+b的结果也会加入上下文环境,但由于结果为16位,还是上下文环境的最大位宽,所以不会进行拓展),最后右端的16位结果将被赋值给左端的sumA。

sumB = a + b;

        执行上面的语句时,前面的步骤与sumA时相同,当操作数sumB,a,b都被加入上下文环境后,最大的位宽为17位(sumB),所以操作数a,b首先将被位补0拓展至17位(这是在任何运算开始前进行的),然后执行加法得到17位的结果,因此保留了进位(此时子表达式a+b的结果也会加入上下文环境,但由于结果为17位,还是上下文环境的最大位宽,所以不会进行拓展),最后右端的17位结果被赋值给左端的sumB。

        如果熟悉C语言的读者,可能已经发现Verilog的位宽拓展与C语言的类型转换之间的区别,下面是一个类似的例子。

#include #include int main() { int a = INT_MAX; int b = 1; // 两个int类型相加, 即使将结果赋值给long long类型,也会溢出 long long result = a + b; printf("result: %lld\n", result); return 0; } 输出: result: -2147483648

        关于C语言的类型转换的更详细内容,可以参考这篇博客C语言:表达式运算的类型转换。

        下面将用更多的例子进一步学习Verilog中的位宽拓展。

例2  reg [5 : 0] a = 6'b010101; reg [3 : 0] b = 4'b1111; reg [7 : 0] c; c = a & b;

        在例2中,a&b作为子表达式成为了=的右操作数,它是上下文决定位宽的,且我们根据图1又可以知道&操作符两边的操作数也是上下文决定位宽,也就是说,和例1的+操作符一样(这相当于进行了嵌套),如果一个表达式作为子表达式被加入上下文环境(作为操作符=的右操作数),且表达式自己也是上下文决定表达式,那么表达式中的所有操作数a,b都会被加入上下文环境。

        例2的计算过程为:操作数c,a,b都会被加入上下文环境,最大的位宽是8位,所以操作数a,b首先被补零拓展至8位,然后执行按位与运算,根据图2,运算结果仍然是8位(此时子表达式a&b的结果也会加入上下文环境,但由于结果为8位,还是上下文环境的最大位宽,所以不会进行拓展),最后将8位结果赋值给左端的c;过程如下面所示。

例3  reg [5 : 0] a = 6'b010101; reg [3 : 0] b = 4'b1111; reg [7 : 0] c; c = a & (& b);

        在例3中,只有最后一步是有变化的,按位与操作符&的右操作数被换成了一个缩减与运算表达式,如果其仍然是上下文决定表达式,那这和上面的两个例子没有任何差别,即a,b,c都会被加入上下文环境。但我们从图1、图2都可以注意到,缩减与操作符是自决定表达式!这时的运算规则就有变化,操作符&中的操作数b不会被加入a、c所在的上下文环境,而是创建了一个新的上下文环境,在本例中该环境中只有操作数b,因此其位宽是由自己决定,即b的位宽就是6位,但(&b)作为子表达式是操作符&的右操作数,子表达式运算结果的位宽还是会被加入上下文环境(换句话说,其中一个上下文环境中存在操作数a、c和作为整体的(&b),另一个上下文环境中只存在操作数b)。根据图1、图2的运算规则,(&b)的运算结果是1位,小于c的8位,此时最大位宽依然是8位,所以(&b)的结果和操作数a都会被补零拓展至8位,然后进行按位与运算,结果是8位,最后赋值给c。过程如下面所示。

        上例中,出现了一个子表达式整体加入上下文环境中的情况,这时子表达式的结果可能会进行位宽拓展, 这一般出现在子表达式是自决定表达式的情况,因为对于上下文决定表达式,运算结果的位宽取决于其操作数的位宽,而操作数的位宽已经根据上下文环境进行了位宽拓展(在所有运算开始之前)。

例4 reg [15 : 0] a, b, answer; answer = (a + b) >> 1;

        如果理解了上面三个例子,那么例4也是游刃有余。在看答案前,先自己可以按照规则尝试着推导出表达式的运算过程。

答案:

        这里涉及到了一个混合操作符——右移>>,由规则我们可以知道,此操作符的左操作数即被移操作数是上下文决定的,右操作数即移位量是自决定的。我们可以首先找到被加入上下文环境的所有操作数,a和b作为+操作符的操作数,是上下文决定的,(a+b)作为整体作为移位操作符的左操作数也是上下文决定的,(a+b)>>1作为整体是=操作符的右操作数,也是上下文决定的,因此操作数answer,a,b都被加入上下文环境中,这三个变量的位宽都是一样的,所以他们三个在运算前不会有拓展。因此首先执行表达式a+b,根据规则,结果为16位,进位被丢失(此时表达式a+b的结果也会加入上下文环境,但由于结果为16位,还是上下文环境的最大位宽,所以不会进行拓展),然后表达式a+b结果再右移一位,最高位补0,结果为还是16位(此时表达式(a+b)>>1的结果也会加入上下文环境,但由于结果为16位,所以不会进行位宽拓展),最后赋值给同为16位的操作数answer。移位表达式中的1(不加基数,位宽的常数)默认是32位,且为自决定操作数,相当于创建了一个新的上下文环境,其中只有操作数1。

        如果想要进位不被丢失应该怎么样?你可以先自己想一想,方法有很多。

把表达式a+b;改成a+b+0;因为操作数0(32位)此时和操作数a,b一样被加入了上下文环境,所以此时最大位宽为32,所以a,b都会被补零拓展到32位,所以进位得以保留,三个数相加的结果任然是32位,移位后结果任然是32位,最后将32位值赋值给16位的answer,高位被截断。

把a+b;改成a+b+17'b0;原因同上,此时a,b被补零拓展至17位,所以相加后能保留进位。

把reg [15:0]answer;改为reg [16:0]answer;原因也是类似的,=左端的answer被添加到上下文环境后,最大位宽变为17,此时a,b被补零拓展至17位,所以相加后能保留进位。

例5 reg [15 : 0] a, b, answer; answer = (a + b + 17'b0) >> (1'b1 + 1'b1);

        例5与例4的区别在于,例5中的移位值(自决定操作数)变成了一个子表达式。由于自决定操作数创建了一个新的上下文环境,其中的两个操作数是1'b1,所以它们不会位宽拓展,进位不会保留,结果仍然为1位,即1'b0,最后的结果不会被移位。其余操作数answer,a,b,17'b0的分析与例4相同。

例6  reg [3 : 0] a, b, c; reg [4 : 0] d; initial begin a = 9; b = 8; c = 1; $display("answer = %b", c ? (a & b) : d); end

        运行结果是什么,为什么是这样?(注意,这里的表达式没有了赋值运算符,这是与之前的例子最大的区别)

        运行结果如下所示:

answer = 01000

答案:

        从规则中我们知道,三目运算符的第一个操作数(条件项)是自决定的,而第二和第三操作数是上下文环境决定,而在这里第二操作数又是一个上下文决定子表达式,根据上下文嵌套的规则,操作数a,b和d都被加入上下文环境,最大位宽为5。操作数c是是自决定的,创建了一个新的上下文环境,该环境中只有操作数c。所以a,b被补零拓展为5位,然后执行与运算得到5位结果01000,然后根据c为1,最后表达式的结果为01000。

例7  reg [3 : 0] a; reg [5 : 0] b; reg [15 : 0] c; initial begin a = 4'hF; b = 6'hA; $display("a * b = %h", a * b); c = {a ** b}; $display("a ** b = %h", c); c = a ** b; $display("c = %h", c); end

        运行结果是什么,为什么是这样? 

        运行结果如下所示:

a * b = 16 a ** b = 1 c = ac61

答案:

        在第一个$display系统函数中,只有一个简单的乘法表达式,又因为操作符*两边的操作数是上下文决定的,操作数a和b加入上下文环境中,最大的位宽为6位,所以操作数a首先被补零拓展到6位,然后和操作数b相乘,结果根据规则,乘法结果也是6位,乘法的真实结果为96h也就是10010110b,但是因为此时结果只取低六位即010110,所以以16进制显示值是16。

        第二个$display系统函数用来显示c值,c = {a ** b};我们表达式a**b被放在了拼接运算符里面,这看上去拼接运算符并没有什么影响,如果你是这么觉得,就会得到意想不到的答案。**乘方运算符的第一个操作数(底数)是上下文决定的,而第二个操作数(指数)是自决定的,但在这里表达式a**b是作为自决定表达式拼接运算符{}的子表达式,所以操作数a会加入另一个上下文环境,其中只存在操作数a。就像例3一样,{a ** b}的结果会作为=运算符的右操作数和操作数c被加入上下文环境,因此操作数a在进行乘方运算前不会位宽拓展。按照规则,乘方运算结果的位宽与底数位宽相同,所以结果为4位,取真结果二进制数1000011001000011000010101010110001100001的低4位,也就是0001,上下文环境中最大位宽为16位,所以0001被补零拓展至0000000000000001,最后赋值给c。

        最后一个$display系统函数,展示的是没有拼接运算符{}的赋值结果,此时操作数a和c都被加入上下文环境,a首先被拓展至16位,然后执行乘方,乘方运算结果的位宽与底数位宽相同,所以结果为16位,取真结果二进制数1000011001000011000010101010110001100001的低16位,也就是ac61h。

        相信通过以上几个例子,你已经可以自己解决关于表达式运算过程中的位宽问题了,但还有一个问题,为什么这里面遇到的都是补零拓展呢,什么时候会遇到符号拓展呢?但这又成为了一个新的专题,感兴趣的读者可以参看下面的文章。

Verilog基础:表达式符号的确定_日晨难再的博客-CSDN博客如果所有操作数有符号,结果才是有符号的。有两个系统函数就是为了调整表达式符号,分别是$signed和$unsigned,它们的返回值就是被转换符号后的数值。否则必须在基数前声明s标志才表示这个常数为有符号的,如3'd4是无符号数,3'sd4是有符号数。拼接操作符的结果是无符号的,不管操作数是否有符号,即使是只有一个有符号操作数也是如此。域选的结果是无符号数,不管域选操作数是否有符号,即使域选的结果是整个向量。比较操作符的结果是无符号的,不管操作数是否有符号。位选的结果是无符号数,不管位选操作数是否有符号。https://blog.csdn.net/weixin_45791458/article/details/128840843?spm=1001.2014.3001.5502



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3